﻿using System;
using System.Collections;
using Parseq.Core;

namespace Parseq.Compiler
{

    class SymbolTable : IExpFactory  
    {
        private Hashtable refs = new Hashtable();
        private Hashtable defs = new Hashtable(); // note that we'll share the rule parsers @ token parsers in a single symbol table... so we will need to be able to 
        private Hashtable parsers = new Hashtable(); 
        public SymbolTable()
        {
        }

        public void Add(IExp exp)
        {
            if (exp is TokenExp)
            {
                this.AddTokenDef((TokenExp)exp);
            }
            else
            {
                throw new ArgumentException(string.Format("Unsupported Exp {0} for symbol table", exp));
            }
        }

        private void AddTokenDef(TokenExp exp) {
            // go through the whole exp to determine what's inside. 
            // first of all - the definition cannot have been previously added - this should be true. 
            string id = exp.ID;
            if (this.refs.Contains(id))
            {
                // it's time to pop it... 
                this.refs.Remove(id); // what happens after you pop it? 
            }
            // go through the sequence. 
            this.processExp(exp);
            if (this.defs.Contains(id))
            {
                throw new ArgumentException(string.Format("Duplicate token definition {0}", id));
            }
            this.defs.Add(id, exp); // under what situation will we modify the definition? I think we won't be modifying any such thing! 
            // what we will do though is to use IDExp to generate the actual underlying reference!.  
            this.parsers.Add(id, new TokenParser(id)); // a default choice parser - of which we will populate later! 
            // how do we know what to do when building out a TokenParser2? 
        }

        /********************************************************************************
         * go through the IExp definitions to ensure that we have the right references & definitions
         * this builds a table of references and remove them as they are being defined, so by the end 
         * there should be nothing leaving in the refs hashtable. 
         */
        private void processExp(IExp exp)
        {
            if (exp is TokenExp)
            {
                // this directly adds to the defs.
                if (this.defs.Contains(((TokenExp)exp).ID))
                {
                    throw new ArgumentException(string.Format("duplicate token definition {0}", ((TokenExp)exp).ID));
                }
                else
                {
                    // let's make sure we process the inner! 
                    this.processExp((IExp)exp.Inner); 
                }
            } else if (exp is IDExp) {
                if (this.defs.Contains(exp.Inner))
                {
                    // do we need to do anything here? maybe not. 
                } else
                {
                    this.refs[exp.Inner] = exp; 
                }
            } else if (exp is SequenceExp) {
                foreach (IExp inner in (IExp[])exp.Inner)
                {
                    // we want to handle the processing of the inner loops. 
                    this.processExp(inner); 
                }
            } else if (exp is ChoiceExp) {
                foreach (IExp inner in (IExp[])exp.Inner)
                {
                    // we want to handle the processing of the inner loops. 
                    this.processExp(inner);
                }
            }
            else if (exp is RepeatExp)
            {
                this.processExp((IExp)exp.Inner); // the inner Exp has potential to be some other types of exps. 
            }
            else if (exp is LiteralExp)
            {
            }
            else  
            {
                // do nothing... hmm... is this true? 
                throw new ArgumentException(string.Format("unknown exp to process {0}", exp));
            }
        }

        /********************************************************************************
         * convert IExp over to BaseParser
         * take care of references, both acyclic & cyclic as the Parser is first built to create the original references and then modified. 
         */
        public void Build()
        {
            // if we have any undefined refs we are in trouble! 
            if (this.refs.Count > 0)
            {
                throw new ArgumentException(string.Format("Referenced but undefined tokens/rules {0}", Util.String.ArrayToString(this.undefinedTokens()))); 
            }

            // the goal now is to go through each of the exps & generate the parsers required to populate them! 
            foreach (DictionaryEntry keyVal in this.defs)
            {
                string id = (string)keyVal.Key; 
                IExp exp = (IExp)keyVal.Value;
                ((TokenParser)this.parsers[id]).Inner = this.BuildParser(exp); 
            }
        }

        public BaseParser BuildParser(IExp exp)
        {
            if (exp is TokenExp)
            {
                // we need to build a *TokenParser2 here! or use this to store it in a token object! 
                return this.BuildParser((IExp)exp.Inner);
            }
            if (exp is IDExp)
            {
                return (BaseParser)this.parsers[exp.Inner];
            }
            else if (exp is LiteralExp)
            {
                // converting the literal over to the final results.
                return Combinator.Chars((string)exp.Inner);
            }
            else if (exp is SequenceExp)
            {
                return this.buildSequenceParser((SequenceExp)exp);
            }
            else if (exp is ChoiceExp)
            {
                // we need to create a choiceExp... 
                return this.buildChoiceParser((ChoiceExp)exp);
            }
            else if (exp is RepeatExp)
            {
                return this.buildRepeatParser((RepeatExp)exp);
            }
            else
            {
                throw new ArgumentException(string.Format("Unknown Exp {0}", exp));
            }
        }

        private ChoiceParser buildChoiceParser(ChoiceExp exp)
        {
            ChoiceParser parser = Combinator.Choice();
            return this.buildChoiceParser(exp, parser); 
        }

        private ChoiceParser buildChoiceParser(ChoiceExp exp, ChoiceParser parser)
        {
            foreach (IExp inner in (IList)exp.Inner)
            {
                // generate the 
                parser.Add(this.BuildParser(inner));
            }
            return parser; 
        }

        private SequenceParser buildSequenceParser(SequenceExp exp)
        {
            SequenceParser parser = Combinator.Sequence();
            return this.buildSequenceParser(exp, parser);
        }

        private SequenceParser buildSequenceParser(SequenceExp exp, SequenceParser parser)
        {
            foreach (IExp inner in (IList)exp.Inner)
            {
                parser.Add(this.BuildParser(inner));
            }
            return parser;
        }

        private RepeatParser buildRepeatParser(RepeatExp exp)
        {
            return Combinator.Repeat(this.BuildParser((IExp)exp.Inner), exp.Min, exp.Max); 
        }

        private string[] undefinedTokens()
        {
            ArrayList list = new ArrayList();
            foreach (DictionaryEntry pair in this.refs)
            {
                list.Add(pair.Key);
            }
            return (string[])list.ToArray(typeof(string));
        }
    }
}
